#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////////////
// CCNode
@interface CCNode {
}
+(id)alloc;
-(id)init;
-(void)addChild:(id)child;
-(void)addChild:(id)child z:(int)zOrder;
@end

@implementation CCNode 
+(id)alloc {
  return malloc(sizeof(self));
}

-(id)init{
  return self;
}

-(void)addChild:(id)child {
}

-(void)addChild:(id)child z:(int)zOrder {

}
@end

////////////////////////////////////////////////////////////////////////////////
// CCScene
@interface CCScene : CCNode {

}
@end
@implementation CCScene 

@end

////////////////////////////////////////////////////////////////////////////////
// CCLayer
@interface CCLayer : CCNode {

}
@end
@implementation CCLayer
@end

////////////////////////////////////////////////////////////////////////////////
// CCSprite
@interface CCSprite : CCNode {
}

@end

@implementation CCSprite

@end

////////////////////////////////////////////////////////////////////////////////
// CCSpriteBatchNode
@interface CCSpriteBatchNode : CCNode {
}

@end

@implementation CCSpriteBatchNode

@end

////////////////////////////////////////////////////////////////////////////////
// CCLuaLayer
@interface CCLuaLayer : CCLayer {
}

@end

@implementation CCLuaLayer

@end


#define UNUSED(x) ((void)(x));

static int xl_game_SwitchToScene(lua_State * L);

#define MAKE_GAME_ACTION(x)  {#x, xl_game_##x}
static const struct luaL_reg game_lib[] = {
    MAKE_GAME_ACTION(SwitchToScene),
    {NULL, NULL}
};

static int xl_scene_LoadTexture(lua_State *L);
static int xl_scene_CreateAnimate(lua_State *L);
static int xl_scene_CreateSprite(lua_State *L);
static int xl_scene_SpriteRunAction(lua_State *L);
static int xl_scene_CreateRouteToAction(lua_State *L);
static int xl_scene_CreateCallFuncAction(lua_State *L);
static int xl_scene_CreateMoveToAction(lua_State *L);
#define MAKE_SCENE_ACTION(x)  {#x, xl_scene_##x}
static const struct luaL_reg scene_lib[] = {
    MAKE_SCENE_ACTION(LoadTexture),
    MAKE_SCENE_ACTION(CreateAnimate),
    MAKE_SCENE_ACTION(CreateSprite),
    MAKE_SCENE_ACTION(SpriteRunAction),
    MAKE_SCENE_ACTION(CreateRouteToAction),
    MAKE_SCENE_ACTION(CreateMoveToAction),
    MAKE_SCENE_ACTION(CreateCallFuncAction),
    {NULL, NULL}
};

#define GAME_OBJECT_LIST_TABLE_NAME "game_object_list"

static void luaopen_gamelib(lua_State * L)
{
    luaL_openlib(L, "game",  game_lib,  0);
    luaL_openlib(L, "scene", scene_lib, 0);
}

static int xl_scene_LoadTexture(lua_State *L)
{
  const char * texture = lua_tostring(L, -1);
  //TODO:load
  UNUSED(texture);
  return 0;
}

static int xl_scene_CreateAnimate(lua_State *L)
{


  return 0;
}

static int xl_scene_CreateSprite(lua_State *L)
{

  return 0;
}

static int xl_scene_SpriteRunAction(lua_State *L)
{
  return 0;
}

static int xl_scene_CreateRouteToAction(lua_State *L)
{
  return 0;
}

static int xl_scene_CreateCallFuncAction(lua_State *L)
{
  return 0;
}

static int xl_scene_CreateMoveToAction(lua_State *L)
{
  return 0;
}

/*
int lua_RunGame(lua_State * L, const char * script_file)
{
    if (luaL_dofile(L, script_file) == 0) {
        return 0;
    }
    return -1;
}

void RunScene(LuaScene * scene)
{
    if (scene == 0) {
        return;
    }

}
*/

int LuaTable_Get(lua_State * L, const char * key)
{
  lua_getfield(L, -1, key);
  if (lua_isnil(L, -1)) {
    lua_pop(L, 1);
    return -1;
  }
  return 0;
}

int LuaTable_GetInt(lua_State * L, const char * key, int * val)
{
  lua_getfield(L, -1, key);
  int ret = 0;
  if (lua_isnumber(L, -1)) {
    if (val) *val = lua_tointeger(L, -1);
    ret = 1;
  }
  lua_pop(L, 1);
  return ret;
}

int LuaTable_GetFloat(lua_State * L, const char * key, float * val)
{
  lua_getfield(L, -1, key);
  int ret = 0;
  if (lua_isnumber(L, -1)) {
    if (val) *val = lua_tonumber(L, -1);
    ret = 1;
  }
  lua_pop(L, 1);
  return ret;
}

int LuaTable_GetIntTable(lua_State * L, const char * key, ...);
int LuaTable_GetFloatTable(lua_State * L, const char * key, ...);

int LuaTable_ForEach(lua_State * L, int (*cb)(lua_State*, void *), void * p)
{
  // empty table
  if (lua_isnil(L, -1)) {
    return 0;
  }

  lua_pushnil(L);
  while(lua_next(L, -2)) {
    int top = lua_gettop(L);
    if (cb(L, p) != 0) {
      // pop key value and return
      assert(top == lua_gettop(L));
      lua_pop(L, 2);
      return -1;
    }
    assert(top == lua_gettop(L));
    lua_pop(L, 1);
  }
  return 0;
}

int LuaTable_SetInt(lua_State * L, const char * key, int val)
{
  lua_pushinteger(L, val);
  lua_setfield(L, -2, key);
  return 0;
}

int LuaTable_SetFloat(lua_State * L, const char * key, float val)
{
  lua_pushnumber(L, val);
  lua_setfield(L, -2, key);
  return 0;
}

int LuaTable_SetFloatTable(lua_State * L, const char * key, ...);
int LuaTable_SetIntTable(lua_State * L, const char * key, void * val);

int LuaTable_SetUserData(lua_State * L, const char * key, void * val)
{
  lua_pushlightuserdata(L, val);
  lua_setfield(L, -2, key);
  return 0;
}

#define COCO_ID "_cc_id"
#define CREATE_OBJECT_BY_LUA_TABLE(Type, ParentNode) \
  do {\
    if (LuaTable_Get(L, #Type) == 0) { \
      assert(lua_istable(L, -1)); \
      LuaTable_ForEach(L, Add##Type, ParentNode);\
      lua_pop(L, 1); \
    } \
  } while(0);


static int AddTexture(lua_State * L, void * p)
{
  printf("create Texture: %s\n", lua_tostring(L, -2));
  CCNode * parent = (CCNode*)p;
  UNUSED(parent);

  const char * texture = lua_tostring(L, -1);

  //TODO:load frame cache
  UNUSED(texture);

  //LuaTable_SetUserData(L, COCO_ID, layer);
  return 0;
}

static int AddAnimation(lua_State * L, void * p)
{
  printf("create Animation: %s\n", lua_tostring(L, -2));
  CCNode * parent = (CCNode*)p;
  UNUSED(parent);

  //LuaTable_SetUserData(L, COCO_ID, layer);
  return 0;
}

static int AddSprite(lua_State * L, void * p)
{
  printf("create Sprite: %s\n", lua_tostring(L, -2));
  CCNode * node = (CCNode*)p;
  CCSprite * sprite = 0;
  //TODO: create sprite

  LuaTable_SetUserData(L, COCO_ID, sprite);

  int zOrder = 0;
  if (LuaTable_GetInt(L, "z", &zOrder) ) {
    [node addChild:sprite z:zOrder];
  } else {
    [node addChild:sprite];
  }
  return 0;
}

static int AddMenu(lua_State * L, void * p)
{
  printf("create Menu: %s\n", lua_tostring(L, -2));
  CCNode * parent = (CCNode*)p;
  UNUSED(parent);

  //LuaTable_SetUserData(L, COCO_ID, layer);
  return 0;
}

static int AddSpriteSheet(lua_State * L, void * p)
{
  printf("create spriteSheet: %s\n", lua_tostring(L, -2));
  CCNode * node = (CCNode*)p;

  // read texture
  if (LuaTable_Get(L, "Texture") != 0) {
    return -1;
  }

  if (!lua_isstring(L, -1)) {
    lua_pop(L, 1);
    return -1;
  }
  const char * texture = lua_tostring(L, -1);
  //TODO: create sprite sheet by texture
  UNUSED(texture);
  //spriteSheet = [[CCSpriteBatchNode alloc] init];
  CCSpriteBatchNode * spriteSheet = 0;
  lua_pop(L, 1);

  // set id
  LuaTable_SetUserData(L, COCO_ID, spriteSheet);

  // create sprite
  CREATE_OBJECT_BY_LUA_TABLE(Sprite, spriteSheet);

  // check zOrder
  int zOrder = 0;
  if (LuaTable_GetInt(L, "z", &zOrder)) {
    [node addChild:spriteSheet z:zOrder];
  } else {
    [node addChild:spriteSheet];
  }
  return 0;
}

#ifdef LOAD_ADDON_SCRIPT
static int AddScript(lua_State * L, void * p)
{
  CCNode * parent = (CCNode*)p;
  UNUSED(parent);

  const char * script = lua_tostring(L, -1);
  printf("load script : %s\n", script);
  if (luaL_dofile(L, script) != 0) {
    return -1;
  }
  return 0;
}
#endif

static CCLayer * CreateLayerFromLuaTable(lua_State * L);

int AddLayer(lua_State * L, void * p)
{
  printf("create Layer : %s\n", lua_tostring(L, -2));
  CCNode * parent = (CCNode*)p;
  CCLayer * layer = CreateLayerFromLuaTable(L);

  int zOrder = 0;
  if (LuaTable_GetInt(L, "z", &zOrder)) {
    [parent addChild:layer z:zOrder];
  } else {
    [parent addChild:layer];
  }
  return 0;
}

static CCLayer * CreateLayerFromLuaTable(lua_State * L)
{
  // create main layer
  CCLayer * layer = [[CCLuaLayer alloc] init];
  LuaTable_SetUserData(L, COCO_ID, layer);

  CREATE_OBJECT_BY_LUA_TABLE(Sprite,      layer);
  CREATE_OBJECT_BY_LUA_TABLE(Menu,        layer);
  CREATE_OBJECT_BY_LUA_TABLE(SpriteSheet, layer);
  CREATE_OBJECT_BY_LUA_TABLE(Layer,       layer);
  return layer;
}

static CCLayer * CreateLayerFromScript(lua_State * L, const char * script)
{
  //TODO: clean version
  lua_pushnil(L); lua_setglobal(L, "Texture");
  lua_pushnil(L); lua_setglobal(L, "Animation");
#ifdef LOAD_ADDON_SCRIPT
  lua_pushnil(L); lua_setglobal(L, "Script");
#endif
  lua_pushnil(L); lua_setglobal(L, "Layer");
  lua_pushnil(L); lua_setglobal(L, "OnTouchBegin");
  lua_pushnil(L); lua_setglobal(L, "OnToucheMove");
  lua_pushnil(L); lua_setglobal(L, "OnTouchEnd");
  lua_pushnil(L); lua_setglobal(L, "OnTouchCancel");

  // load file
  if (luaL_dofile(L, script) != 0) {
    return 0;
  }

  // load global table
  lua_gettable (L, LUA_GLOBALSINDEX);

#ifdef LOAD_ADDON_SCRIPT
  // load addon script
  if (LuaTable_Get(L, "Script") == 0) {
    LuaTable_ForEach(L, AddScript, 0);
    lua_pop(L, 1);
  }
#endif

  // Load Texture
  if (LuaTable_Get(L, "Texture") == 0) {
    LuaTable_ForEach(L, AddTexture, 0);
    lua_pop(L, 1);
  }

  // Load Animation
  if (LuaTable_Get(L, "Animation") == 0) {
    LuaTable_ForEach(L, AddAnimation, 0);
    lua_pop(L, 1);
  }

  printf("create main layer\n");
  CCLayer * layer = CreateLayerFromLuaTable(L);
  lua_pop(L, 1);
  return layer;
}

static int xl_game_SwitchToScene(lua_State * L)
{
  const char * script = lua_tostring(L, -1);
  if (script == 0) {
    return 0;
  }

  CCScene * scene = [[CCScene alloc] init];

  CCLayer * layer = CreateLayerFromScript(L, script);
  if (layer == 0) {
    return 0;
  }

  [scene addChild:layer];
  //TODO: switch to scene

  return 0;
}

int main(int argc, char * argv[])
{
    lua_State * L = luaL_newstate();
    luaL_openlibs(L);
    luaopen_gamelib(L);

    int i;
    for (i = 1; i < argc; i++) {
        if (luaL_dofile(L, argv[i]) != 0) {
            break;
        }
    }
    lua_close(L);

    return 0;
}
